﻿using System;
using System.Collections.Specialized;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Moq;

namespace Machinery.Extensions
{
    public static class ControllerExtensions
    {

        /// <summary>
        /// Make the Request of the controller present itself as a AJAX request. 
        /// This is handy when testing involves the IsAjaxRequest property.
        /// </summary>
        /// <param name="controller"></param>
        public static void PresentRequestAsAjaxRequest(this Controller controller)
        {
            var mockRequest = Mock.Get(controller.HttpContext.Request);
            mockRequest.Setup(r => r["X-Requested-With"]).Returns("XMLHttpRequest");
        }


        /// <summary>
        /// Defines what url to fake in case the Controller asks for a referrer.
        /// </summary>
        /// <param name="request"></param>
        /// <param name="uri"></param>
        public static void SetupReferrer(this HttpRequestBase request, Uri uri)
        {
            if (uri == null)
                throw new ArgumentNullException("uri");

            var mock = Mock.Get(request);

            mock.Setup(req => req.UrlReferrer).Returns(uri);
        }

        public static HttpContextBase FakeHttpContext()
        {
            var context = new Mock<HttpContextBase>();
            var request = new Mock<HttpRequestBase>();
            var response = new Mock<HttpResponseBase>();
            var session = new Mock<HttpSessionStateBase>();
            var server = new Mock<HttpServerUtilityBase>();

            context.Setup(ctx => ctx.Request).Returns(request.Object);
            context.Setup(ctx => ctx.Response).Returns(response.Object);
            context.Setup(ctx => ctx.Session).Returns(session.Object);
            context.Setup(ctx => ctx.Server).Returns(server.Object);

            var requestCookies = new HttpCookieCollection();
            request.Setup(req => req.Cookies).Returns(requestCookies);

            var responseCookies = new HttpCookieCollection();
            response.Setup(resp => resp.Cookies).Returns(responseCookies);

            var formVariables = new NameValueCollection();
            request.Setup(req => req.Form).Returns(formVariables);

            return context.Object;
        }

        public static void SetFakeContextOn(this Controller controller, HttpContextBase httpContext)
        {
            var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
            controller.ControllerContext = context;

            
        }

        public static HttpContextBase FakeHttpContext(this string url)
        {
            var context = FakeHttpContext();
            SetupRequestUrlGet(context.Request, url);
            return context;
        }

        public static void SetFakeControllerContext(this Controller controller)
        {
            var httpContext = FakeHttpContext();
            controller.SetFakeContextOn(httpContext);
        }

        public static void SetupRequestUrlGet(this HttpRequestBase request, string url)
        {
            if (url == null)
                throw new ArgumentNullException("url");

            if (!url.StartsWith("~/"))
                throw new ArgumentException("Sorry, we expect a virtual url starting with \"~/\".");

            var mock = Mock.Get(request);

            mock.Setup(req => req.QueryString)
                .Returns(GetQueryStringParameters(url));
            mock.Setup(req => req.AppRelativeCurrentExecutionFilePath)
                .Returns(GetUrlFileName(url));
            mock.Setup(req => req.PathInfo)
                .Returns(String.Empty);
        }

        static string GetUrlFileName(string url)
        {
            return url.Contains("?") ? url.Substring(0, url.IndexOf("?")) : url;
        }

        static NameValueCollection GetQueryStringParameters(string url)
        {
            if (!url.Contains("?"))
                return null;

            var parameters = new NameValueCollection();

            var parts = url.Split("?".ToCharArray());
            var keys = parts[1].Split("&".ToCharArray());

            foreach (var key in keys)
            {
                var part = key.Split("=".ToCharArray());
                parameters.Add(part[0], part[1]);
            }

            return parameters;
        }

        /// <summary>
        /// Fires OnActionExecuting on all ActionFilterAttributes on a given method.
        /// </summary>
        /// <param name="actionName">Name of the action that you want to fire the fiters for.</param>
        public static void FireEventsAndActionFilters(this Controller controller, string actionName)
        {
            controller.FireOnActionExecuting();
            
            var actionExecutingContextMock = new Mock<ActionExecutingContext>();
            actionExecutingContextMock.Setup(c => c.Controller).Returns(controller);

            var flags = BindingFlags.Instance | BindingFlags.Public;
            var method = controller.GetType().GetMethod(actionName, flags);
            if (method == null)
                throw new NullReferenceException("No method named " + actionName + " was found on " + controller.GetType().Name);
            foreach (Attribute a in method.GetCustomAttributes(true))
                if (a is ActionFilterAttribute)
                    (a as ActionFilterAttribute).OnActionExecuting(actionExecutingContextMock.Object);
        }

        /// <summary>
        /// Fire the OnActionExecuting on the controller.
        /// </summary>
        public static void FireOnActionExecuting(this Controller controller)
        {
            var fakeActionDescriptor = new Mock<ActionDescriptor>().Object;
            var fakeActionParameters = new RouteValueDictionary();
            var actionExecutingContext = new ActionExecutingContext(controller.ControllerContext, fakeActionDescriptor, fakeActionParameters);
            var methodOnActionExecuting = controller.GetType().GetMethod("OnActionExecuting", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
            methodOnActionExecuting.Invoke(controller, new object[] { actionExecutingContext });
        }




    
    }
}
